package simulation;

import framework.Canvas;
import framework.Force;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import massobjects.BaseMass;
import massobjects.FixedMass;
import springobjects.Spring;


/**
 * Simulates objects moving around in a bounded environment.
 * 
 * @author David Howdyshell, Zack Hopping
 */
public class Simulation {
    private static final int PIXELS_PER_RESIZE = 10;
    private static final int MOUSESPRING_LENGTH = 100;

    private List<Assembly> myAssemblies;
    private Canvas myContainer;
    private GlobalForceHandler myForceHandler;
    private int myExtraSize;
    private FixedMass myMouseMass;
    private Spring myMouseSpring;

    /**
     * Create a Canvas with the given size.
     * 
     * @param container - pointer to the canvas object
     */
    public Simulation (Canvas container) {
        myAssemblies = new ArrayList<Assembly>();
        myContainer = container;
        myForceHandler = new GlobalForceHandler(this);
        myExtraSize = 0;
        myMouseMass = new FixedMass(0, 0, 0);
    }

    /**
     * Add a new assembly to this simulation.
     * 
     * @param assem - the new assembly
     */
    public void add (Assembly assem) {
        myAssemblies.add(assem);
    }

    /**
     * Increase size of the playable area.
     */
    public void addSize () {
        myExtraSize++;
    }

    /**
     * Reduce the size of the playable area.
     */
    public void reduceSize () {
        int roomToDownsize = (getSize().width / 2)
                + ((myExtraSize - 1) * PIXELS_PER_RESIZE);
        if (roomToDownsize > 0) {
            myExtraSize--;
        }
    }

    /**
     * Gets difference in canvas size and playable area.
     * 
     * @return number of pixels difference
     */
    public int getSizeModifier () {
        return myExtraSize * PIXELS_PER_RESIZE;
    }

    /**
     * Clears all assemblies from the simulation.
     */
    public void clearSimulation () {
        myAssemblies.clear();
    }

    /**
     * Checks key presses from user input.
     * 
     * @param key - user pressed key
     */
    public void parseKey (int key) {
        myForceHandler.setToggleForce(key);
    }

    /**
     * Gets pointer to object storing the global forces.
     * 
     * @return myForceHandler
     */
    public GlobalForceHandler getForceHandler () {
        return myForceHandler;
    }

    /**
     * Paint side walls, mouse, and all assemblies.
     * 
     * @param pen used to paint shape on the screen
     */
    public void paint (Graphics2D pen) {
        drawSideWalls(pen);
        drawMouse(pen);
        for (Assembly a : myAssemblies) {
            a.paint(pen);
        }
    }

    /**
     * Draws a red mass for mouse if it is clicked and corresponding spring.
     * 
     * @param pen - drawing object
     */
    private void drawMouse (Graphics2D pen) {
        if (myContainer.getMouseClicked()) {
            myMouseMass.paint(pen, Color.RED);
            if (myMouseSpring != null) {
                myMouseSpring.paint(pen);
            }
        }
    }

    /**
     * Fills in black rectangles on side if playable area smaller than canvas.
     * 
     * @param pen - still for drawing things...
     */
    private void drawSideWalls (Graphics2D pen) {
        pen.setColor(Color.BLACK);
        pen.fillRect(0, 0, -getSizeModifier(), getSize().height);
        pen.fillRect(getSize().width + getSizeModifier(), 0,
                -getSizeModifier(), getSize().height);
    }

    /**
     * Called by each step of timer, multiple times per second.
     * This should update the state of the animated shapes by just
     * a little so they appear to move over time.
     * 
     * @param dt - elapsed time
     */
    public void update (double dt) {
        updateMouseMass();
        updateMouseSpring(dt);
        for (Assembly a : myAssemblies) {
            a.update(this, dt);
        }
    }

    /**
     * Updates the mass object represting the mouse.
     */
    private void updateMouseMass () {
        Point mousePos = myContainer.getLastMousePosition();
        myMouseMass.setCenter(mousePos.x, mousePos.y);
        if (myContainer.getNewMouseClick()) {
            myContainer.clearMouseClick();
            myMouseSpring = new Spring(myMouseMass, getNearestMass(),
                    MOUSESPRING_LENGTH, 1);
        }
        if (!myContainer.getMouseClicked()) {
            myMouseSpring = null;
        }
    }

    /**
     * Updates the mouse spring if it exists.
     * 
     * @param dt - elapsed time
     */
    private void updateMouseSpring (double dt) {
        if (myMouseSpring != null) {
            myMouseSpring.update(dt);
        }
    }

    /**
     * Returns the nearest mass from all assemblies to the current mouse
     * location.
     * 
     * @return - pointer to closest mass
     */
    private BaseMass getNearestMass () {
        BaseMass closestMass = myMouseMass;
        double closestDist = Double.MAX_VALUE;
        for (Assembly a : myAssemblies) {
            List<BaseMass> masses = a.getAllMasses();
            for (BaseMass m : masses) {
                double curDist = Force.distanceBetween(myMouseMass.getCenter(),
                        m.getCenter());
                if (curDist < closestDist) {
                    closestMass = m;
                    closestDist = curDist;
                }
            }
        }
        return closestMass;
    }

    /**
     * Returns size (in pixels) of the game area.
     */
    public Dimension getSize () {
        return myContainer.getSize();
    }

}
